;
; MHC archiver for MenuetOS - very fast compression tool
;
; version 0.09
;
; Written by Nikita Lesnikov (nlo_one@mail.ru, Republic of Belarus, Sluck)
;
   
;==============================================================================
   
;
; Brief file format description:
;
;                  +-----------+------------------------+
;  File structure: | Method ID | Compressed data        |
;                  +-----------+------------------------+
;
;  Methods list:
;
;    0. LZP (order-2 specified specially for *.ASM,*.RAW and MeOS executables)
;
;  New methods can be easily added without loss of compatibility
; with older versions
;
   
;==============================================================================
   
; SYSTEM HEADER
   
use32
   
  org 0x0
  db "MENUET01"
  dd 0x01
  dd ENTRANCE
  dd MHC_END
  dd 0x300000  ; 3 megs of memory needed
  dd 0x2FF000
  dd 0x0
  dd 0x0
   
; CODE AREA
   
ENTRANCE:
   
; ======== user interface =========
   
   
 call draw_window             ; draw the window
   
 still:
   
 mov eax,10                   ; wait for event
 int 0x40
   
 cmp eax,1                    ; redraw?
 jnz no_redraw
 call draw_window
 no_redraw:
   
 cmp eax,2                    ; key pressed?
 jz key
   
 cmp eax,3                    ; button pressed?
 jz button
   
 jmp still
   
 ; Key handler
   
 key:
 mov eax,2   ; read it
 int 0x40
 shr eax,8
   
 cmp byte [editstate],0
 jz  still
   
 cmp al,8    ; backspace
 jnz no_bksp
 cmp byte [editpos],0
 jz  no_del_last
 dec byte [editpos]
 xor ebx,ebx
 mov bl,byte [editpos]
 add ebx,cmfile
 cmp byte [editstate],2
 jnz no_add_base_1
 add ebx,12
 no_add_base_1:
 mov byte [ebx],32
 no_del_last:
 call draw_info
 jmp still
 no_bksp:
   
 cmp al,13            ; enter
 jnz no_enter
 mov byte [editstate],0
 call draw_info
 jmp still
 no_enter:
   
 cmp eax,dword 31
 jbe no_lit
 cmp eax,dword 95
 jb  capital
 sub eax,32
 capital:
 xor ebx,ebx
 mov bl,byte [editpos]
 add ebx,cmfile
 cmp byte [editstate],2
 jnz no_add_base_2
 add ebx,12
 no_add_base_2:
 mov byte [ebx],al
 inc byte [editpos]
 cmp byte [editpos],12
 jnz no_null_state
 mov byte [editstate],0
 no_null_state:
 call draw_info
 no_lit:
   
 jmp still
   
 ; Button handler
   
 button:
   
 mov eax,17
 int 0x40
   
 cmp ah,1
 jnz no_quit
 mov eax,-1
 int 0x40
 no_quit:
   
 cmp ah,4
 jnz nofirst
 cld
 mov byte [editstate],1
 mov edi,cmfile
 mov eax,0x20202020
 mov ecx,3
 rep stosd
 mov byte [editpos],0
 mov byte [msgid],0
 call draw_info
 nofirst:
   
 cmp ah,5
 jnz nosecond
 cld
 mov byte [editstate],2
 mov edi,iofile
 mov eax,0x20202020
 mov ecx,3
 rep stosd
 mov byte [editpos],0
 mov byte [msgid],0
 call draw_info
 nosecond:
   
 cmp ah,2
 jnz no_compress
 call compress
 no_compress:
   
 cmp ah,3
 jnz no_decompress
 call decompress
 no_decompress:
   
 cmp ah,6
 jnz no_delete_io
 pusha
 mov eax,32
 mov ebx,iofile
 int 0x40
 popa
 no_delete_io:
   
 cmp ah,7
 jnz  no_delete_archive
 pusha
 mov eax,32
 mov ebx,cmfile
 int 0x40
 popa
 no_delete_archive:
   
 jmp still
   
 ; WINDOW DRAW
   
 draw_window:
   
 mov eax,12  ; Start redrawing
 mov ebx,1
 int 0x40
   
 xor eax,eax           ; Define window
 mov ebx,100*65536+240
 mov ecx,100*65536+130
 mov edx,0x02AAAAAA
 mov esi,0x80777777
 mov edi,0x00777777
 int 0x40
   
 mov eax,4              ; Draw all needed texts
 mov ebx,8*65536+8
 mov ecx,0x00FFFFFF
 mov edx,title
 mov esi,arclab-title
 int 0x40
   
 xor ecx,ecx
 mov edx,arclab
 mov esi,unplab-arclab
 add ebx,10*65536+28
 int 0x40
   
 mov edx,unplab
 mov esi,fin_text-unplab
 add ebx,18
 int 0x40
   
 pusha
   
 mov eax,8            ; Buttons
 mov ebx,222*65536+10
 mov ecx,6*65536+10
 mov edx,1
 mov esi,0x555555
 int 0x40
   
 mov ebx,15*65536+100
 mov ecx,70*65536+13
 inc edx
 int 0x40
   
 inc edx
 add ebx,110*65536
 int 0x40
   
 inc edx
 mov ebx,214*65536+11
 mov ecx,33*65536+11
 int 0x40
   
 inc edx
 add ecx,18*65536
 int 0x40
   
 inc edx
 mov ebx,15*65536+100
 mov ecx,86*65536+13
 int 0x40
   
 inc edx
 add ebx,110*65536
 int 0x40
   
 popa
   
 mov ecx,0x00FFFFFF
 mov edx,keylab
 mov esi,dellab-keylab
 add ebx,19
 int 0x40
   
 mov edx,dellab
 mov esi,title-dellab
 add ebx,16
 int 0x40
   
 call draw_info
   
 mov eax,12          ; Finish redrawing
 mov ebx,2
 int 0x40
   
 ret
   
 draw_info:          ; Draw filenames and compressor state
   
 activecolor equ 0x00112299
   
 pusha ; Save registers
   
 mov eax,13               ; Clean draw area
 mov ebx,127*65536+85
 mov ecx,33*65536+33
 mov edx,0x00AAAAAA
 int 0x40
   
 mov eax,4 ; Draw filenames
 mov ebx,134*65536+36
 mov edx,cmfile
 xor ecx,ecx
 mov esi,12
 cmp byte [editstate],1
 jnz no_active_1
 mov ecx,activecolor
 no_active_1:
 int 0x40
 xor ecx,ecx
 cmp byte [editstate],2
 jnz no_active_2
 mov ecx,activecolor
 no_active_2:
 add ebx,18
 add edx,12
 int 0x40
   
 mov eax,13             ; Clean info area
 mov ebx,14*65536+210
 mov ecx,107*65536+14
 mov edx,0x00AAAAAA
 int 0x40
   
 cmp byte [msgid],0     ; Draw info string
 jz notype
 mov ebx,16*65536+110
 xor ecx,ecx
 mov esi,16
 mov al, byte [msgid]
 dec al
 shl al,4
 xor ah,ah
 xor edx,edx
 mov dx,ax
 add edx,msgtable
 mov eax,4
 int 0x40
 notype:
   
 popa ; Restore registers
   
 ret
   
 ; interface data
   
 keylab db "    COMPRESS         DECOMPRESS"
  dellab db "   DELETE I/O       DELETE *.MHC"
  title db "MHC 0.09"
 arclab db "COMPRESSED FILE:"
 unplab db "INPUT/OUTPUT FILE:"
 fin_text:
   
 cmfile db "FILENAME.MHC"
 iofile db "FILENAME.XYZ"
   
 editstate db 0
 editpos db 0
 msgid db 0
   
 msgtable:
 db "COMPRESSING...  "
 db "DECOMPRESSING..."
 db "I/O NOT FOUND!  "
 db "*.MHC NOT FOUND!"
 db "INVALID METHOD! "
   
; ======== compression/decompression engine ========
   
; Adresses declaration
   
 hashtable equ  MHC_END
 ifile     equ  hashtable+65536*4
 ofile     equ  ifile+1000000
   
 compress:   ; File compression
   
 call fill_filebufs
   
 mov eax,6
 mov ebx,iofile
 xor ecx,ecx
 mov edx,ecx
 not edx
 mov esi,ifile
 int 0x40
   
 cmp eax,0xFFFFFFFF
 jnz  compress_filefound              ; i/o file not found
 mov byte [msgid],3
 call draw_info
 ret
   
 compress_filefound:
   
 mov byte [msgid],1
 call draw_info
   
 jmp lzp_compress                    ; compress with order-2 LZP
 compress_dumpdata:
   
 push edx
   
 mov eax,32
 mov ebx,cmfile
 int 0x40
   
 mov eax,33
 pop edx
 mov ebx,cmfile
 mov ecx,ofile
 xor esi,esi
 int 0x40
   
 mov byte [msgid],0
 call draw_info
   
 ret
   
   
 decompress: ; File decompression
   
 call fill_filebufs
   
 mov  eax,6
 mov ebx,cmfile
 xor ecx,ecx
 mov edx,ecx
 not edx
 mov esi,ofile
 int 0x40
   
 cmp eax,0xFFFFFFFF
 jnz  decompress_filefound              ; *.mhc file not found
 mov byte [msgid],4
 call draw_info
 ret
   
 decompress_filefound:
   
 cmp byte [ofile],0                     ; Invalid method!
 jz  right_method
 mov byte [msgid],5
 call draw_info
 ret
   
 right_method:
 mov byte [msgid],2
 call draw_info
   
 jmp lzp_decompress
 decompress_dumpdata:
   
 push edx
   
 mov eax,32
 mov ebx,iofile
 int 0x40
   
 mov eax,33
 pop edx
 mov ebx,iofile
 mov ecx,ifile
 xor esi,esi
 int 0x40
   
 mov byte [msgid],0
 call draw_info
   
 ret
   
 fill_filebufs:             ; Fill filebufs with garbage to simplify matching
 pusha
 cld
 mov eax,0xF7D9A03F         ; <- "magic number" :) just garbage...
 mov ecx,2000000/4
 mov edi,ifile
 rep stosd
 popa
 ret
   
; ==== algorithms section ====
   
; Method 0: LZP compression algorithm
   
 lzp_compress:           ; EDX - how much bytes to dump
   
 cld                     ; clear direction flag
   
 mov esi,ifile           ; init pointers
 mov edi,ofile
   
 push eax                ; write header: ID0+4bfilesize => total 5 bytes
 xor eax,eax
 stosb
 pop eax
 stosd
   
 pusha                   ; fill hash table
 mov eax,ifile
 mov edi,hashtable
 mov ecx,65536
 rep stosd
 popa
   
 add eax,esi              ; calculate endpointer
 mov dword [endpointer],eax
   
 movsw                    ; copy three bytes
 movsb
   
 mov dword [controlp],edi
 inc edi
   
 mov byte [controld],0
 mov byte [controlb],0
   
 c_loop:
 cmp dword [endpointer],esi  ; check end of file
 ja  c_loop_ok
 jmp finish_c_loop
 c_loop_ok:
   
 call chash
 call compare
 jz   two_match_c
   
 lodsb
 mov byte [literal],al
 call chash
 call compare
 jz   lit_match_c
   
 mov  al,0
 call putbit
 mov  al,byte [literal]
 stosb
 movsb
 jmp  end_c_loop
   
 lit_match_c:
 mov al,1
 call putbit
 mov al,0
 call putbit
 mov al,byte [literal]
 stosb
 jmp encode_match
   
 two_match_c:
 mov al,1
 call putbit
 call putbit
   
 encode_match:
 call incpos
 call compare
 jz one_c
 mov al,0
 call putbit
 jmp end_c_loop
 one_c:
   
 call incpos
 mov  al,1
 call putbit
   
 call compare
 jnz ec1
 call incpos
 call compare
 jnz ec2
 call incpos
 call compare
 jnz ec3
 call incpos
 mov al,1
 call putbit
 call putbit
 call compare
 jnz ec4
 call incpos
 call compare
 jnz ec5
 call incpos
 call compare
 jnz ec6
 call incpos
 call compare
 jnz ec7
 call incpos
 call compare
 jnz ec8
 call incpos
 call compare
 jnz ec9
 call incpos
 call compare
 jnz ec10
 call incpos
   
 mov al,1
 call putbit
 call putbit
 call putbit
 xor  ecx,ecx
   
 match_loop_c:
 cmp  esi,dword [endpointer]
 jae   out_match_loop_c
 call compare
 jnz  out_match_loop_c
 inc  ecx
 call incpos
 jmp  match_loop_c
 out_match_loop_c:
   
 mov al,0xFF
 out_lg:
 cmp ecx,255
 jb  out_lg_out
 stosb
 sub ecx,255
 jmp out_lg
 out_lg_out:
 mov al,cl
 stosb
 jmp end_c_loop
   
 ec10:
 mov al,1
 call putbit
 call putbit
 mov al,0
 call putbit
 jmp end_c_loop
   
 ec9:
 mov al,1
 call putbit
 mov al,0
 call putbit
 mov al,1
 call putbit
 jmp end_c_loop
   
 ec8:
 mov al,1
 call putbit
 mov al,0
 call putbit
 call putbit
 jmp end_c_loop
   
 ec7:
 mov al,0
 call putbit
 mov al,1
 call putbit
 call putbit
 jmp end_c_loop
   
 ec6:
 mov al,0
 call putbit
 mov al,1
 call putbit
 mov al,0
 call putbit
 jmp end_c_loop
   
 ec5:
 mov al,0
 call putbit
 call putbit
 mov al,1
 call putbit
 jmp end_c_loop
   
 ec4:
 mov al,0
 call putbit
 call putbit
 call putbit
 jmp end_c_loop
   
 ec3:
 mov al,1
 call putbit
 mov al,0
 call putbit
 jmp end_c_loop
   
 ec2:
 mov al,0
 call putbit
 mov al,1
 call putbit
 jmp end_c_loop
   
 ec1:
 mov al,0
 call putbit
 call putbit
   
 end_c_loop:
 jmp c_loop
   
 finish_c_loop:
   
 mov eax,dword [controlp] ; store last tagbyte
 mov bl,byte [controld]
 mov [eax], byte bl
   
 sub edi,ofile ; calculate dump size
 mov edx,edi
   
 jmp compress_dumpdata
   
; LZP decompression algorithm
   
 lzp_decompress:                        ; EDX - how much bytes to dump
   
 cld
   
 mov edi,ifile
 mov esi,ofile+1
   
 pusha                   ; fill hash table
 mov eax,ifile
 mov edi,hashtable
 mov ecx,65536
 rep stosd
 popa
   
 lodsd
   
 mov ebx,edi
 add ebx,eax
 mov dword [endpointer],ebx
   
 movsw
 movsb
   
 lodsb
 mov byte [controld],al
 mov byte [controlb],0
   
 d_loop:
 cmp dword [endpointer],edi
 ja d_loop_ok
 jmp finish_d_loop
 d_loop_ok:
   
 call getbit
 cmp  al,0
 jnz  match_d
 call dhash
 movsb
 call dhash
 movsb
 jmp end_d_loop
   
 match_d:
   
 call getbit
 cmp  al,0
 jnz  no_literal_before_match
 call dhash
 movsb
 no_literal_before_match:
   
 call dhash
 mov ecx,1
 call copymatch
   
 call getbit
 cmp  al,0
 jz   end_d_loop
 mov  ecx,1
 call copymatch
 call getbit
 cmp  al,0
 jz   dc2
 mov  ecx,2
 call copymatch
 call getbit
 cmp  al,0
 jz   end_d_loop
 mov  ecx,1
 call copymatch
 call getbit
 cmp  al,0
 jz   dc4
 mov  ecx,4
 call copymatch
 call getbit
 cmp  al,0
 jz   dc5
 call getbit
 cmp  al,0
 jz   dc6
 mov  ecx,3
 call copymatch
   
 do:
 lodsb
 xor  ecx,ecx
 mov  cl,al
 call copymatch
 cmp  al,0xFF
 jnz  end_do
 jmp do
 end_do:
 jmp end_d_loop
   
 dc6:
 mov ecx,2
 call copymatch
 jmp  end_d_loop
   
 dc5:
 call getbit
 cmp  al,0
 jz   ndc5
 mov  ecx,1
 call copymatch
 ndc5:
 jmp  end_d_loop
   
 dc4:
 call getbit
 cmp  al,0
 jz   ndc4
 call getbit
 mov  ecx,3
 cmp  al,1
 jz   ndcc4
 dec  ecx
 ndcc4:
 call copymatch
 jmp  end_d_loop
 ndc4:
 call getbit
 cmp  al,0
 jz   ndccc4
 mov  ecx,1
 call copymatch
 ndccc4:
 jmp  end_d_loop
   
 dc2:
 call getbit
 cmp al,0
 jz  ndc2
 mov ecx,1
 call copymatch
 ndc2:
   
 end_d_loop:
 jmp d_loop
 finish_d_loop:
   
 mov edx, dword [ofile+1]
   
 jmp decompress_dumpdata
   
; LZP subroutines
   
 putbit:                  ; bit -> byte tag, AL holds bit for output
 pusha
 mov cl,byte [controlb]
 shl al,cl
 mov bl,byte [controld]
 or  bl,al
 mov byte [controld],bl
 inc cl
 cmp cl,8
 jnz just_increment
 mov byte [controlb],0
 mov byte [controld],0
 push edi
 mov  edi, dword [controlp]
 mov  al,bl
 stosb
 pop  edi
 mov dword [controlp],edi
 popa
 inc edi
 ret
 just_increment:
 mov byte [controlb],cl
 popa
 ret
   
 getbit:                       ; tag byte -> bit, AL holds input
 push ecx
 mov al,byte [controld]
 mov cl,byte [controlb]
 shr al,cl
 and al,1
 inc cl
 cmp cl,8
 jnz just_increment_d
 mov byte [controlb],0
 push eax
 lodsb
 mov byte [controld],al
 pop  eax
 pop  ecx
 ret
 just_increment_d:
 mov byte [controlb],cl
 pop ecx
 ret
   
 chash:                        ; calculate hash -> mp -> fill position
 pusha
 xor  eax,eax
 mov  al, byte [esi-1]
 mov  ah, byte [esi-2]
 shl  eax,2
 add  eax,hashtable
 mov  edx,dword [eax]
 mov  dword [mp],edx
 mov  dword [eax],esi
 popa
 ret
   
 dhash:                        ; calculate hash -> mp -> fill position
 pusha
 xor  eax,eax
 mov  al, byte [edi-1]
 mov  ah, byte [edi-2]
 shl  eax,2
 add  eax,hashtable
 mov  edx,dword [eax]
 mov  dword [mp],edx
 mov  dword [eax],edi
 popa
 ret
   
 copymatch:                    ; ECX bytes from [mp] to [rp]
 push esi
 mov  esi,dword [mp]
 rep  movsb
 mov  dword [mp],esi
 pop  esi
 ret
   
 compare:                      ; compare [mp] with [cpos]
 push edi
 push esi
 mov  edi,dword [mp]
 cmpsb
 pop  esi
 pop  edi
 ret
   
 incpos:
 inc  dword [mp]
 inc  esi
 ret
   
   
; LZP algorithm data
   
 endpointer     dd      0
 controlp       dd      0
 controlb       db      0
 controld       db      0
 mp  dd 0
 literal        db      0
   
MHC_END: ; the end... - Nikita Lesnikov (nlo_one)
   
   